/*-------------------------------------------------------------------------
 *
 * dirmod.c
 *      directory handling functions
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *    This includes replacement versions of functions that work on
 *    Win32 (NT4 and newer).
 *
 * IDENTIFICATION
 *      src/port/dirmod.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

/* Don't modify declarations in system headers */
#if defined(WIN32) || defined(__CYGWIN__)
#undef rename
#undef unlink
#endif

#include <unistd.h>
#include <sys/stat.h>

#if defined(WIN32) || defined(__CYGWIN__)
#ifndef __CYGWIN__
#include <winioctl.h>
#else
#include <windows.h>
#include <w32api/winioctl.h>
#endif
#endif

#if defined(WIN32) || defined(__CYGWIN__)

/*
 *    pgrename
 */
int
pgrename(const char *from, const char *to)
{// #lizard forgives
    int            loops = 0;

    /*
     * We need to loop because even though PostgreSQL uses flags that allow
     * rename while the file is open, other applications might have the file
     * open without those flags.  However, we won't wait indefinitely for
     * someone else to close the file, as the caller might be holding locks
     * and blocking other backends.
     */
#if defined(WIN32) && !defined(__CYGWIN__)
    while (!MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING))
#else
    while (rename(from, to) < 0)
#endif
    {
#if defined(WIN32) && !defined(__CYGWIN__)
        DWORD        err = GetLastError();

        _dosmaperr(err);

        /*
         * Modern NT-based Windows versions return ERROR_SHARING_VIOLATION if
         * another process has the file open without FILE_SHARE_DELETE.
         * ERROR_LOCK_VIOLATION has also been seen with some anti-virus
         * software. This used to check for just ERROR_ACCESS_DENIED, so
         * presumably you can get that too with some OS versions. We don't
         * expect real permission errors where we currently use rename().
         */
        if (err != ERROR_ACCESS_DENIED &&
            err != ERROR_SHARING_VIOLATION &&
            err != ERROR_LOCK_VIOLATION)
            return -1;
#else
        if (errno != EACCES)
            return -1;
#endif

        if (++loops > 100)        /* time out after 10 sec */
            return -1;
        pg_usleep(100000);        /* us */
    }
    return 0;
}


/*
 *    pgunlink
 */
int
pgunlink(const char *path)
{
    int            loops = 0;

    /*
     * We need to loop because even though PostgreSQL uses flags that allow
     * unlink while the file is open, other applications might have the file
     * open without those flags.  However, we won't wait indefinitely for
     * someone else to close the file, as the caller might be holding locks
     * and blocking other backends.
     */
    while (unlink(path))
    {
        if (errno != EACCES)
            return -1;
        if (++loops > 100)        /* time out after 10 sec */
            return -1;
        pg_usleep(100000);        /* us */
    }
    return 0;
}

/* We undefined these above; now redefine for possible use below */
#define rename(from, to)        pgrename(from, to)
#define unlink(path)            pgunlink(path)
#endif                            /* defined(WIN32) || defined(__CYGWIN__) */


#if defined(WIN32) && !defined(__CYGWIN__)    /* Cygwin has its own symlinks */

/*
 *    pgsymlink support:
 *
 *    This struct is a replacement for REPARSE_DATA_BUFFER which is defined in VC6 winnt.h
 *    but omitted in later SDK functions.
 *    We only need the SymbolicLinkReparseBuffer part of the original struct's union.
 */
typedef struct
{
    DWORD        ReparseTag;
    WORD        ReparseDataLength;
    WORD        Reserved;
    /* SymbolicLinkReparseBuffer */
    WORD        SubstituteNameOffset;
    WORD        SubstituteNameLength;
    WORD        PrintNameOffset;
    WORD        PrintNameLength;
    WCHAR        PathBuffer[FLEXIBLE_ARRAY_MEMBER];
} REPARSE_JUNCTION_DATA_BUFFER;

#define REPARSE_JUNCTION_DATA_BUFFER_HEADER_SIZE   \
        FIELD_OFFSET(REPARSE_JUNCTION_DATA_BUFFER, SubstituteNameOffset)


/*
 *    pgsymlink - uses Win32 junction points
 *
 *    For reference:    http://www.codeproject.com/KB/winsdk/junctionpoints.aspx
 */
int
pgsymlink(const char *oldpath, const char *newpath)
{
    HANDLE        dirhandle;
    DWORD        len;
    char        buffer[MAX_PATH * sizeof(WCHAR) + offsetof(REPARSE_JUNCTION_DATA_BUFFER, PathBuffer)];
    char        nativeTarget[MAX_PATH];
    char       *p = nativeTarget;
    REPARSE_JUNCTION_DATA_BUFFER *reparseBuf = (REPARSE_JUNCTION_DATA_BUFFER *) buffer;

    CreateDirectory(newpath, 0);
    dirhandle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE,
                           0, 0, OPEN_EXISTING,
                           FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0);

    if (dirhandle == INVALID_HANDLE_VALUE)
        return -1;

    /* make sure we have an unparsed native win32 path */
    if (memcmp("\\??\\", oldpath, 4) != 0)
        snprintf(nativeTarget, sizeof(nativeTarget), "\\??\\%s", oldpath);
    else
        strlcpy(nativeTarget, oldpath, sizeof(nativeTarget));

    while ((p = strchr(p, '/')) != NULL)
        *p++ = '\\';

    len = strlen(nativeTarget) * sizeof(WCHAR);
    reparseBuf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
    reparseBuf->ReparseDataLength = len + 12;
    reparseBuf->Reserved = 0;
    reparseBuf->SubstituteNameOffset = 0;
    reparseBuf->SubstituteNameLength = len;
    reparseBuf->PrintNameOffset = len + sizeof(WCHAR);
    reparseBuf->PrintNameLength = 0;
    MultiByteToWideChar(CP_ACP, 0, nativeTarget, -1,
                        reparseBuf->PathBuffer, MAX_PATH);

    /*
     * FSCTL_SET_REPARSE_POINT is coded differently depending on SDK version;
     * we use our own definition
     */
    if (!DeviceIoControl(dirhandle,
                         CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_ANY_ACCESS),
                         reparseBuf,
                         reparseBuf->ReparseDataLength + REPARSE_JUNCTION_DATA_BUFFER_HEADER_SIZE,
                         0, 0, &len, 0))
    {
        LPSTR        msg;

        errno = 0;
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                      FORMAT_MESSAGE_IGNORE_INSERTS |
                      FORMAT_MESSAGE_FROM_SYSTEM,
                      NULL, GetLastError(),
                      MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
                      (LPSTR) &msg, 0, NULL);
#ifndef FRONTEND
        ereport(ERROR,
                (errcode_for_file_access(),
                 errmsg("could not set junction for \"%s\": %s",
                        nativeTarget, msg)));
#else
        fprintf(stderr, _("could not set junction for \"%s\": %s\n"),
                nativeTarget, msg);
#endif
        LocalFree(msg);

        CloseHandle(dirhandle);
        RemoveDirectory(newpath);
        return -1;
    }

    CloseHandle(dirhandle);

    return 0;
}

/*
 *    pgreadlink - uses Win32 junction points
 */
int
pgreadlink(const char *path, char *buf, size_t size)
{// #lizard forgives
    DWORD        attr;
    HANDLE        h;
    char        buffer[MAX_PATH * sizeof(WCHAR) + offsetof(REPARSE_JUNCTION_DATA_BUFFER, PathBuffer)];
    REPARSE_JUNCTION_DATA_BUFFER *reparseBuf = (REPARSE_JUNCTION_DATA_BUFFER *) buffer;
    DWORD        len;
    int            r;

    attr = GetFileAttributes(path);
    if (attr == INVALID_FILE_ATTRIBUTES)
    {
        _dosmaperr(GetLastError());
        return -1;
    }
    if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
    {
        errno = EINVAL;
        return -1;
    }

    h = CreateFile(path,
                   GENERIC_READ,
                   FILE_SHARE_READ | FILE_SHARE_WRITE,
                   NULL,
                   OPEN_EXISTING,
                   FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
                   0);
    if (h == INVALID_HANDLE_VALUE)
    {
        _dosmaperr(GetLastError());
        return -1;
    }

    if (!DeviceIoControl(h,
                         FSCTL_GET_REPARSE_POINT,
                         NULL,
                         0,
                         (LPVOID) reparseBuf,
                         sizeof(buffer),
                         &len,
                         NULL))
    {
        LPSTR        msg;

        errno = 0;
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                      FORMAT_MESSAGE_IGNORE_INSERTS |
                      FORMAT_MESSAGE_FROM_SYSTEM,
                      NULL, GetLastError(),
                      MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
                      (LPSTR) &msg, 0, NULL);
#ifndef FRONTEND
        ereport(ERROR,
                (errcode_for_file_access(),
                 errmsg("could not get junction for \"%s\": %s",
                        path, msg)));
#else
        fprintf(stderr, _("could not get junction for \"%s\": %s\n"),
                path, msg);
#endif
        LocalFree(msg);
        CloseHandle(h);
        errno = EINVAL;
        return -1;
    }
    CloseHandle(h);

    /* Got it, let's get some results from this */
    if (reparseBuf->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
    {
        errno = EINVAL;
        return -1;
    }

    r = WideCharToMultiByte(CP_ACP, 0,
                            reparseBuf->PathBuffer, -1,
                            buf,
                            size,
                            NULL, NULL);

    if (r <= 0)
    {
        errno = EINVAL;
        return -1;
    }

    /*
     * If the path starts with "\??\", which it will do in most (all?) cases,
     * strip those out.
     */
    if (r > 4 && strncmp(buf, "\\??\\", 4) == 0)
    {
        memmove(buf, buf + 4, strlen(buf + 4) + 1);
        r -= 4;
    }
    return r;
}

/*
 * Assumes the file exists, so will return false if it doesn't
 * (since a nonexistent file is not a junction)
 */
bool
pgwin32_is_junction(const char *path)
{
    DWORD        attr = GetFileAttributes(path);

    if (attr == INVALID_FILE_ATTRIBUTES)
    {
        _dosmaperr(GetLastError());
        return false;
    }
    return ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT);
}
#endif                            /* defined(WIN32) && !defined(__CYGWIN__) */


#if defined(WIN32) && !defined(__CYGWIN__)

#undef stat

/*
 * The stat() function in win32 is not guaranteed to update the st_size
 * field when run. So we define our own version that uses the Win32 API
 * to update this field.
 */
int
pgwin32_safestat(const char *path, struct stat *buf)
{
    int            r;
    WIN32_FILE_ATTRIBUTE_DATA attr;

    r = stat(path, buf);
    if (r < 0)
    {
        if (GetLastError() == ERROR_DELETE_PENDING)
        {
            /*
             * File has been deleted, but is not gone from the filesystem yet.
             * This can happen when some process with FILE_SHARE_DELETE has it
             * open and it will be fully removed once that handle is closed.
             * Meanwhile, we can't open it, so indicate that the file just
             * doesn't exist.
             */
            errno = ENOENT;
            return -1;
        }

        return r;
    }

    if (!GetFileAttributesEx(path, GetFileExInfoStandard, &attr))
    {
        _dosmaperr(GetLastError());
        return -1;
    }

    /*
     * XXX no support for large files here, but we don't do that in general on
     * Win32 yet.
     */
    buf->st_size = attr.nFileSizeLow;

    return 0;
}

#endif
